/*
REWRITTEN FROM SCRATCH BY PUSSYWIZARD, IT OWNS NOW!
*/

#include "ObjectMgr.h"
#include "ScriptMgr.h"
#include "ScriptedCreature.h"
#include "SpellAuraEffects.h"
#include "Group.h"
#include "Spell.h"
#include "icecrown_citadel.h"
#include "Vehicle.h"
#include "GridNotifiers.h"

enum ScriptTexts
{
	// Festergut
	SAY_FESTERGUT_GASEOUS_BLIGHT    = 0,
	SAY_FESTERGUT_DEATH             = 1,

	// Rotface
	SAY_ROTFACE_OOZE_FLOOD          = 2,
	SAY_ROTFACE_DEATH               = 3,

	// Professor Putricide
	SAY_AGGRO                       = 4,
	EMOTE_UNSTABLE_EXPERIMENT       = 5,
	SAY_PHASE_TRANSITION_HEROIC     = 6,
	SAY_TRANSFORM_1                 = 7,
	SAY_TRANSFORM_2                 = 8,    // always used for phase2 change, DO NOT GROUP WITH SAY_TRANSFORM_1
	EMOTE_MALLEABLE_GOO             = 9,
	EMOTE_CHOKING_GAS_BOMB          = 10,
	SAY_KILL                        = 11,
	SAY_BERSERK                     = 12,
	SAY_DEATH                       = 13,
};

enum Spells
{
	// Festergut
	SPELL_RELEASE_GAS_VISUAL                = 69125,
	SPELL_GASEOUS_BLIGHT_LARGE              = 69157,
	SPELL_GASEOUS_BLIGHT_MEDIUM             = 69162,
	SPELL_GASEOUS_BLIGHT_SMALL              = 69164,

	// Rotface - not needed here

	// Professor Putricide
	SPELL_SLIME_PUDDLE_TRIGGER              = 70341,
	SPELL_MALLEABLE_GOO_BALCONY             = 72296,
	SPELL_MALLEABLE_GOO                     = 70852,
	SPELL_UNSTABLE_EXPERIMENT               = 70351,
	SPELL_TEAR_GAS                          = 71617,    // phase transition
	SPELL_TEAR_GAS_CREATURE                 = 71618,
	SPELL_TEAR_GAS_CANCEL                   = 71620,
	SPELL_TEAR_GAS_PERIODIC_TRIGGER         = 73170,
	SPELL_CREATE_CONCOCTION                 = 71621,
	SPELL_GUZZLE_POTIONS                    = 71893,
	SPELL_OOZE_TANK_PROTECTION              = 71770,    // protects the tank
	SPELL_CHOKING_GAS_BOMB                  = 71255,
	SPELL_OOZE_VARIABLE                     = 74118,
	SPELL_GAS_VARIABLE                      = 74119,
	SPELL_UNBOUND_PLAGUE                    = 70911,
	SPELL_UNBOUND_PLAGUE_SEARCHER           = 70917,
	SPELL_PLAGUE_SICKNESS                   = 70953,
	SPELL_UNBOUND_PLAGUE_PROTECTION         = 70955,
	SPELL_MUTATED_PLAGUE                    = 72451,
	SPELL_MUTATED_PLAGUE_CLEAR              = 72618,

	// Slime Puddle
	SPELL_GROW_STACKER                      = 70345,
	SPELL_GROW                              = 70347,
	SPELL_SLIME_PUDDLE_AURA                 = 70343,

	// Gas Cloud
	SPELL_GASEOUS_BLOAT_PROC                = 70215,
	SPELL_GASEOUS_BLOAT                     = 70672,
	SPELL_GASEOUS_BLOAT_PROTECTION          = 70812,
	SPELL_EXPUNGED_GAS                      = 70701,

	// Volatile Ooze
	SPELL_OOZE_ERUPTION                     = 70492,
	SPELL_VOLATILE_OOZE_ADHESIVE            = 70447,
	SPELL_OOZE_ERUPTION_SEARCH_PERIODIC     = 70457,
	SPELL_VOLATILE_OOZE_PROTECTION          = 70530,

	// Choking Gas Bomb
	SPELL_CHOKING_GAS_BOMB_PERIODIC         = 71259,
	SPELL_CHOKING_GAS_EXPLOSION_TRIGGER     = 71280,

	// Mutated Abomination vehicle
	SPELL_ABOMINATION_VEHICLE_POWER_DRAIN   = 70385,
	SPELL_MUTATED_TRANSFORMATION            = 70311,
	SPELL_MUTATED_TRANSFORMATION_DAMAGE     = 70405,
	SPELL_MUTATED_TRANSFORMATION_NAME       = 72401,

	// Unholy Infusion
	SPELL_UNHOLY_INFUSION                   = 71516,
	SPELL_UNHOLY_INFUSION_CREDIT            = 71518,
};

enum PutricideData
{
	DATA_EXPERIMENT_STAGE = 1,
	DATA_PHASE = 2,
	DATA_ABOMINATION = 3,
};

enum Events
{
	EVENT_NONE,
	EVENT_BERSERK,
	EVENT_SLIME_PUDDLE,
	EVENT_UNSTABLE_EXPERIMENT,
	EVENT_GO_TO_TABLE,
	EVENT_TABLE_DRINK_STUFF,
	EVENT_PHASE_TRANSITION,
	EVENT_RESUME_ATTACK,
	EVENT_UNBOUND_PLAGUE,
	EVENT_MALLEABLE_GOO,
	EVENT_CHOKING_GAS_BOMB,
};

#define EVENT_GROUP_ABILITIES 1

enum Points
{
	POINT_FESTERGUT = 366260,
	POINT_ROTFACE   = 366270,
	POINT_TABLE     = 366780,
	POINT_TABLE_COMBAT = 366781,
};

Position const festergutWatchPos = {4324.820f, 3166.03f, 389.3831f, 3.316126f}; //emote 432 (release gas)
Position const rotfaceWatchPos   = {4390.371f, 3164.50f, 389.3890f, 5.497787f}; //emote 432 (release ooze)
Position const tablePos          = {4356.190f, 3262.90f, 389.4820f, 1.483530f};

class AbominationDespawner
{
	public:
		explicit AbominationDespawner(Unit* owner) : _owner(owner) { }

		bool operator()(uint64 guid)
		{
			if (Unit* summon = ObjectAccessor::GetUnit(*_owner, guid))
			{
				if (summon->GetEntry() == NPC_MUTATED_ABOMINATION_10 || summon->GetEntry() == NPC_MUTATED_ABOMINATION_25)
				{
					if (Vehicle* veh = summon->GetVehicleKit())
						veh->RemoveAllPassengers(); // also despawns the vehicle

					// Found unit is Mutated Abomination, remove it
					return true;
				}

				// Found unit is not Mutated Abomintaion, leave it
				return false;
			}

			// No unit found, remove from SummonList
			return true;
		}

	private:
		Unit* _owner;
};

class UnboundPlagueTargetSelector
{
public:
	UnboundPlagueTargetSelector(Creature* source) : _source(source) { }

	bool operator()(WorldObject* object) const
	{
		if (!object)
			return true;
		if (Player* p = object->ToPlayer())
		{
			if (p == _source->GetVictim() || p->GetExactDist(_source) >= 45.0f)
				return true;

			return false;
		}
		return true;
	}
private:
	Creature const* _source;
};

// xinef: malleable goo selector, check for target validity
struct MalleableGooSelector : public std::unary_function<Unit*, bool>
{
    const Unit* me;
    MalleableGooSelector(Unit const* unit) : me(unit) {}

    bool operator()(Unit const* target) const
    {
        if (!me || !target || target->GetTypeId() != TYPEID_PLAYER)
            return false;

        if (me->IsWithinCombatRange(target, 7.0f))
            return false;

        return me->IsValidAttackTarget(target);
    }
};

class boss_professor_putricide : public CreatureScript
{
	public:
		boss_professor_putricide() : CreatureScript("boss_professor_putricide") { }

		struct boss_professor_putricideAI : public BossAI
		{
			boss_professor_putricideAI(Creature* creature) : BossAI(creature, DATA_PROFESSOR_PUTRICIDE)
			{
				bCallEvade = false;
				bEnteredCombat = false;
			}

			uint16 sayFestergutDeathTimer;
			uint16 sayRotfaceDeathTimer;
			bool bCallEvade;
			uint8 _experimentState;
			uint8 _phase;
			bool bChangePhase;
			bool bEnteredCombat; // needed for failing an attempt in JustReachedHome()

			void Reset()
			{
				sayFestergutDeathTimer = 0;
				sayRotfaceDeathTimer = 0;
				_experimentState = 0;
				_phase = 1;
				bChangePhase = false;
				_Reset();
				me->SetReactState(REACT_AGGRESSIVE);
				me->SetStandState(UNIT_STAND_STATE_STAND);

				if (instance->GetBossState(DATA_ROTFACE) == DONE && instance->GetBossState(DATA_FESTERGUT) == DONE)
					me->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
			}

			uint32 GetData(uint32 type) const
			{
				switch (type)
				{
					case DATA_EXPERIMENT_STAGE:
						return (uint32)_experimentState;
					case DATA_PHASE:
						return (uint32)_phase;
					case DATA_ABOMINATION:
						return (uint32)(const_cast<SummonList*>(&summons)->HasEntry(NPC_MUTATED_ABOMINATION_10) || const_cast<SummonList*>(&summons)->HasEntry(NPC_MUTATED_ABOMINATION_25));
				}

				return 0;
			}

			void SetData(uint32 id, uint32 data)
			{
				if (id == DATA_EXPERIMENT_STAGE)
					_experimentState = (data ? 1 : 0);
			}

			void AttackStart(Unit* who)
			{
				if (instance->CheckRequiredBosses(DATA_PROFESSOR_PUTRICIDE))
					BossAI::AttackStart(who);
			}

			bool CanAIAttack(const Unit* target) const
			{
				return me->IsVisible() && target->GetPositionZ() > 388.0f && target->GetPositionZ() < 410.0f && target->GetPositionY() > 3157.1f && target->GetExactDist2dSq(4356.0f, 3211.0f) < 80.0f*80.0f;
			}

			void MoveInLineOfSight(Unit* who)
			{
				if (!me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE))
					BossAI::MoveInLineOfSight(who);
			}

			void EnterCombat(Unit* who)
			{
				Position homePos = me->GetHomePosition();
				if (!instance->CheckRequiredBosses(DATA_PROFESSOR_PUTRICIDE, who->ToPlayer()) || me->GetExactDist2d(&homePos) > 10.0f || !me->IsVisible()) // check home position because during festergut/rotface fight, trigger missile after their death can trigger putricide combat
				{
					me->CombatStop();
					me->RemoveAllAuras();
					return;
				}

				bEnteredCombat = true;
				me->CastSpell(me, SPELL_OOZE_TANK_PROTECTION, true);
				events.Reset();
				events.ScheduleEvent(EVENT_BERSERK, 600000);
				events.ScheduleEvent(EVENT_SLIME_PUDDLE, 10000, EVENT_GROUP_ABILITIES);
				events.ScheduleEvent(EVENT_UNSTABLE_EXPERIMENT, urand(30000, 35000), EVENT_GROUP_ABILITIES);
				if (IsHeroic())
					events.ScheduleEvent(EVENT_UNBOUND_PLAGUE, 20000, EVENT_GROUP_ABILITIES);

				instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_GAS_VARIABLE);
				instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_OOZE_VARIABLE);

				me->setActive(true);
				Talk(SAY_AGGRO);
				DoZoneInCombat(me);
				instance->SetBossState(DATA_PROFESSOR_PUTRICIDE, IN_PROGRESS);
				instance->SetData(DATA_NAUSEA_ACHIEVEMENT, uint32(true));
			}

			void JustReachedHome()
			{
				_JustReachedHome();
				if (bEnteredCombat)
				{
					bEnteredCombat = false;
					instance->SetBossState(DATA_PROFESSOR_PUTRICIDE, FAIL);
				}

				instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_GAS_VARIABLE);
				instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_OOZE_VARIABLE);
			}

			void KilledUnit(Unit* victim)
			{
				if (victim->GetTypeId() == TYPEID_PLAYER)
					Talk(SAY_KILL);
			}

			void JustDied(Unit* /*killer*/)
			{
				_JustDied();
				Talk(SAY_DEATH);

                if (Is25ManRaid() && me->HasAura(SPELL_SHADOWS_FATE))
                    DoCastAOE(SPELL_UNHOLY_INFUSION_CREDIT, true); // ReqTargetAura in dbc

				me->CastSpell((Unit*)NULL, SPELL_MUTATED_PLAGUE_CLEAR, true);
			}

			void JustSummoned(Creature* summon)
			{
				if (summon->GetEntry() != 38308 && summon->GetEntry() != 38309 && (!me->IsInCombat() || me->IsInEvadeMode()))
				{
					summon->DespawnOrUnsummon(1);
					return;
				}

				summons.Summon(summon);

				switch (summon->GetEntry())
				{
					case NPC_GROWING_OOZE_PUDDLE:
						// blizzard casts this spell 7 times initially (confirmed in sniff)
						for (uint8 i = 0; i < 7; ++i)
							summon->CastSpell(summon, SPELL_GROW, true);
						return;
					case NPC_GAS_CLOUD:
						// no possible aura seen in sniff adding the aurastate
						summon->ModifyAuraState(AURA_STATE_UNKNOWN22, true);
						summon->CastSpell(summon, SPELL_GASEOUS_BLOAT_PROC, true);
						summon->SetReactState(REACT_PASSIVE);
						break;
					case NPC_VOLATILE_OOZE:
						// no possible aura seen in sniff adding the aurastate
						summon->ModifyAuraState(AURA_STATE_UNKNOWN19, true);
						summon->CastSpell(summon, SPELL_OOZE_ERUPTION_SEARCH_PERIODIC, true);
						summon->SetReactState(REACT_PASSIVE);
						break;
					case NPC_CHOKING_GAS_BOMB:
						summon->CastSpell(summon, SPELL_CHOKING_GAS_BOMB_PERIODIC, true);
						summon->CastSpell(summon, SPELL_CHOKING_GAS_EXPLOSION_TRIGGER, true);
						return;
					case NPC_MUTATED_ABOMINATION_10:
					case NPC_MUTATED_ABOMINATION_25:
						return;
				}

				if (me->IsInCombat())
					summon->SetInCombatWithZone();
			}

			void DamageTaken(Unit*, uint32& /*damage*/, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask)
			{
				if (bChangePhase)
					return;

				switch (_phase)
				{
					case 1:
						if (HealthAbovePct(80))
							return;
						me->SetReactState(REACT_PASSIVE);
						bChangePhase = true;
						break;
					case 2:
						if (HealthAbovePct(35))
							return;
						me->SetReactState(REACT_PASSIVE);
						bChangePhase = true;
						break;
					default:
						break;
				}
			}

			void MovementInform(uint32 type, uint32 id)
			{
				if (type != POINT_MOTION_TYPE)
					return;
				switch (id)
				{
					case POINT_FESTERGUT:
						if (Creature* c = instance->instance->GetCreature(instance->GetData64(DATA_FESTERGUT)))
							if (c->IsInCombat())
							{
								instance->SetBossState(DATA_FESTERGUT, IN_PROGRESS);
								me->SetFacingTo(festergutWatchPos.GetOrientation());
								DoAction(ACTION_FESTERGUT_GAS);
								c->CastSpell(c, SPELL_GASEOUS_BLIGHT_LARGE, true, NULL, NULL, c->GetGUID());
							}
							else
								bCallEvade = true;
						break;
					case POINT_ROTFACE:
						if (Creature* c = instance->instance->GetCreature(instance->GetData64(DATA_ROTFACE)))
							if (c->IsInCombat())
							{
								instance->SetBossState(DATA_ROTFACE, IN_PROGRESS);
								me->SetFacingTo(rotfaceWatchPos.GetOrientation());
							}
							else
								bCallEvade = true;
						break;
					case POINT_TABLE:
						me->SetFacingTo(tablePos.GetOrientation());
						me->SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_STATE_USE_STANDING);
						break;
					case POINT_TABLE_COMBAT:
						me->SetFacingTo(tablePos.GetOrientation());
						me->GetMotionMaster()->Clear(false);
						me->GetMotionMaster()->MoveIdle();
						events.ScheduleEvent(EVENT_TABLE_DRINK_STUFF, IsHeroic() ? 25000 : 0);
						break;
				}
			}

			void DoAction(int32 action)
			{
				switch (action)
				{
					case ACTION_FESTERGUT_COMBAT:
						me->GetMotionMaster()->MoveCharge(festergutWatchPos.GetPositionX(), festergutWatchPos.GetPositionY(), festergutWatchPos.GetPositionZ(), 15.0f, POINT_FESTERGUT);
						break;
					case ACTION_FESTERGUT_DEATH:
						sayFestergutDeathTimer = 4000;
						break;
					case ACTION_FESTERGUT_GAS:
						Talk(SAY_FESTERGUT_GASEOUS_BLIGHT);
						DoCast(me, SPELL_RELEASE_GAS_VISUAL, true);
						break;
					case ACTION_ROTFACE_COMBAT:
						me->GetMotionMaster()->MoveCharge(rotfaceWatchPos.GetPositionX(), rotfaceWatchPos.GetPositionY(), rotfaceWatchPos.GetPositionZ(), 15.0f, POINT_ROTFACE);
						break;
					case ACTION_ROTFACE_DEATH:
						sayRotfaceDeathTimer = 4500;
						break;
					default:
						break;
				}
			}

			void UpdateAI(uint32 diff)
			{
				if (sayFestergutDeathTimer)
				{
					if (sayFestergutDeathTimer <= diff)
					{
						sayFestergutDeathTimer = 0;
						Talk(SAY_FESTERGUT_DEATH);
						EnterEvadeMode();
					}
					else
						sayFestergutDeathTimer -= diff;
				}
				else if (sayRotfaceDeathTimer)
				{
					if (sayRotfaceDeathTimer <= diff)
					{
						sayRotfaceDeathTimer = 0;
						Talk(SAY_ROTFACE_DEATH);
						EnterEvadeMode();
					}
					else
						sayRotfaceDeathTimer -= diff;
				}
				else if (bCallEvade)
				{
					bCallEvade = false;
					EnterEvadeMode();
					return;
				}

				if (!UpdateVictim() || !CheckInRoom())
					return;

				events.Update(diff);

				if (me->HasUnitState(UNIT_STATE_CASTING))
					return;

				if (bChangePhase)
				{
					ChangePhase();
					bChangePhase = false;
					return;
				}

				switch (events.ExecuteEvent())
				{
					case EVENT_BERSERK:
						Talk(SAY_BERSERK);
						DoCast(me, SPELL_BERSERK2);
						break;
					case EVENT_SLIME_PUDDLE:
						{
							std::list<Unit*> targets;
							SelectTargetList(targets, 2, SELECT_TARGET_RANDOM, 0.0f, true);
							if (!targets.empty())
								for (std::list<Unit*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
									me->CastSpell(*itr, SPELL_SLIME_PUDDLE_TRIGGER, true);
							events.ScheduleEvent(EVENT_SLIME_PUDDLE, 35000, EVENT_GROUP_ABILITIES);
						}
						break;
					case EVENT_UNSTABLE_EXPERIMENT:
						Talk(EMOTE_UNSTABLE_EXPERIMENT);
						me->CastSpell(me, SPELL_UNSTABLE_EXPERIMENT, false);
						events.ScheduleEvent(EVENT_UNSTABLE_EXPERIMENT, urand(35000, 40000), EVENT_GROUP_ABILITIES);
						break;
					case EVENT_GO_TO_TABLE:
						me->CastSpell(me, SPELL_TEAR_GAS_PERIODIC_TRIGGER, true);
						me->GetMotionMaster()->MoveCharge(tablePos.GetPositionX(), tablePos.GetPositionY(), tablePos.GetPositionZ(), 15.0f, POINT_TABLE_COMBAT);
						break;
					case EVENT_TABLE_DRINK_STUFF:
						switch (_phase)
						{
							case 2:
							{
								SpellInfo const* spell = sSpellMgr->GetSpellInfo(SPELL_CREATE_CONCOCTION);
								me->CastSpell(me, SPELL_CREATE_CONCOCTION, false);
								events.ScheduleEvent(EVENT_PHASE_TRANSITION, sSpellMgr->GetSpellForDifficultyFromSpell(spell, me)->CalcCastTime() + 2250);
								break;
							}
							case 3:
							{
								SpellInfo const* spell = sSpellMgr->GetSpellInfo(SPELL_GUZZLE_POTIONS);
								me->CastSpell(me, SPELL_GUZZLE_POTIONS, false);
								events.ScheduleEvent(EVENT_PHASE_TRANSITION, sSpellMgr->GetSpellForDifficultyFromSpell(spell, me)->CalcCastTime() + 2250);
								break;
							}
							default:
								break;
						}
						break;
					case EVENT_PHASE_TRANSITION:
						{
							switch (_phase)
							{
								case 2:
									if (Creature* face = me->FindNearestCreature(NPC_TEAR_GAS_TARGET_STALKER, 50.0f))
										me->SetFacingToObject(face);
									me->SetStandState(UNIT_STAND_STATE_KNEEL);
									Talk(SAY_TRANSFORM_1);
									events.ScheduleEvent(EVENT_RESUME_ATTACK, 5500);
									break;
								case 3:
									if (Creature* face = me->FindNearestCreature(NPC_TEAR_GAS_TARGET_STALKER, 50.0f))
										me->SetFacingToObject(face);
									me->SetStandState(UNIT_STAND_STATE_KNEEL);
									Talk(SAY_TRANSFORM_2);
									events.ScheduleEvent(EVENT_RESUME_ATTACK, 8500);
									break;
								default:
									break;
							}
						}
						break;
					case EVENT_RESUME_ATTACK:
						me->SetReactState(REACT_AGGRESSIVE);
						me->SetStandState(UNIT_STAND_STATE_STAND);
						AttackStart(me->GetVictim());
						// remove Tear Gas
						me->RemoveAurasDueToSpell(SPELL_TEAR_GAS_PERIODIC_TRIGGER);
						DoCastAOE(SPELL_TEAR_GAS_CANCEL);
						if (_phase == 3)
							summons.DespawnIf(AbominationDespawner(me));
						instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_GAS_VARIABLE);
						instance->DoRemoveAurasDueToSpellOnPlayers(SPELL_OOZE_VARIABLE);
						break;
					case EVENT_UNBOUND_PLAGUE:
						if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, UnboundPlagueTargetSelector(me)))
						{
							me->CastSpell(target, SPELL_UNBOUND_PLAGUE, false);
							me->CastSpell(target, SPELL_UNBOUND_PLAGUE_SEARCHER, false);
							events.ScheduleEvent(EVENT_UNBOUND_PLAGUE, 90000, EVENT_GROUP_ABILITIES);
						}
						else
							events.ScheduleEvent(EVENT_UNBOUND_PLAGUE, 3500, EVENT_GROUP_ABILITIES);
						break;
					case EVENT_MALLEABLE_GOO:
						if (Is25ManRaid())
						{
							std::list<Unit*> targets;
							SelectTargetList(targets, MalleableGooSelector(me), (IsHeroic() ? 3 : 2), SELECT_TARGET_RANDOM);

							if (!targets.empty())
							{
								Talk(EMOTE_MALLEABLE_GOO);
								for (std::list<Unit*>::iterator itr = targets.begin(); itr != targets.end(); ++itr)
									me->CastSpell(*itr, SPELL_MALLEABLE_GOO, true);
							}
						}
						else
						{
							if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 1, MalleableGooSelector(me)))
							{
								Talk(EMOTE_MALLEABLE_GOO);
								me->CastSpell(target, SPELL_MALLEABLE_GOO, true);
							}
						}
						events.ScheduleEvent(EVENT_MALLEABLE_GOO, urand(25000, 30000), EVENT_GROUP_ABILITIES);
						break;
					case EVENT_CHOKING_GAS_BOMB:
						Talk(EMOTE_CHOKING_GAS_BOMB);
						me->CastSpell(me, SPELL_CHOKING_GAS_BOMB, false);
						events.ScheduleEvent(EVENT_CHOKING_GAS_BOMB, urand(35000, 40000), EVENT_GROUP_ABILITIES);
						break;
					default:
						break;
				}

				DoMeleeAttackIfReady();
			}

			void EnterEvadeMode()
			{
				Position p = me->GetHomePosition();
				if (!me->IsInCombat() && me->GetExactDist2d(&p) > 10.0f)
					me->GetMotionMaster()->MoveCharge(tablePos.GetPositionX(), tablePos.GetPositionY(), tablePos.GetPositionZ(), 15.0f, POINT_TABLE);
				BossAI::EnterEvadeMode();
			}

			void ChangePhase()
			{
				uint32 heroicDelay = (IsHeroic() ? 25000 : 0);
				events.DelayEvents(24000 + heroicDelay, EVENT_GROUP_ABILITIES);
				me->AttackStop();
				if (!IsHeroic())
				{
					me->CastSpell(me, SPELL_TEAR_GAS, false);
					events.ScheduleEvent(EVENT_GO_TO_TABLE, 2500);
				}
				else
				{
					Talk(SAY_PHASE_TRANSITION_HEROIC);
					DoCast(me, SPELL_UNSTABLE_EXPERIMENT, true);
					DoCast(me, SPELL_UNSTABLE_EXPERIMENT, true);
					// cast variables
					if (Is25ManRaid())
					{
						std::list<Unit*> targetList;

						Map::PlayerList const& pl = me->GetMap()->GetPlayers();
						for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
							if (Player* p = itr->GetSource())
								if (!p->IsGameMaster())
									targetList.push_back(p);

						size_t half = targetList.size()/2;
						// half gets ooze variable
						while (half < targetList.size())
						{
							std::list<Unit*>::iterator itr = targetList.begin();
							advance(itr, urand(0, targetList.size() - 1));
							(*itr)->CastSpell(*itr, SPELL_OOZE_VARIABLE, true);
							targetList.erase(itr);
						}
						// and half gets gas
						for (std::list<Unit*>::iterator itr = targetList.begin(); itr != targetList.end(); ++itr)
							(*itr)->CastSpell(*itr, SPELL_GAS_VARIABLE, true);
					}

					me->GetMotionMaster()->MoveCharge(tablePos.GetPositionX(), tablePos.GetPositionY(), tablePos.GetPositionZ(), 15.0f, POINT_TABLE_COMBAT);
				}

				switch (_phase)
				{
					case 1:
						_phase = 2;
						events.ScheduleEvent(EVENT_MALLEABLE_GOO, urand(25000, 28000) + heroicDelay, EVENT_GROUP_ABILITIES);
						events.ScheduleEvent(EVENT_CHOKING_GAS_BOMB, urand(35000, 40000) + heroicDelay, EVENT_GROUP_ABILITIES);
						break;
					case 2:
						_phase = 3;
						events.CancelEvent(EVENT_UNSTABLE_EXPERIMENT);
						break;
					default:
						break;
				}
			}
		};

		CreatureAI* GetAI(Creature* creature) const
		{
			return GetIcecrownCitadelAI<boss_professor_putricideAI>(creature);
		}
};

class npc_putricide_oozeAI : public ScriptedAI
{
	public:
		npc_putricide_oozeAI(Creature* creature, uint32 hitTargetSpellId) : ScriptedAI(creature),
			_hitTargetSpellId(hitTargetSpellId), _newTargetSelectTimer(0)
		{
			targetGUID = 0;
			me->SetReactState(REACT_PASSIVE);
		}

		uint64 targetGUID;

		void SetGUID(uint64 guid, int32 type)
		{
			if (type == -1)
				targetGUID = guid;
		}

		void IsSummonedBy(Unit* /*summoner*/)
		{
			if (InstanceScript* instance = me->GetInstanceScript())
				if (Creature* professor = ObjectAccessor::GetCreature(*me, instance->GetData64(DATA_PROFESSOR_PUTRICIDE)))
				{
					if (!professor->IsInCombat())
						me->DespawnOrUnsummon(1);
					else
						professor->AI()->JustSummoned(me);
				}
		}

		void SelectNewTarget()
		{
			targetGUID = 0;
			me->InterruptNonMeleeSpells(true);
			me->AttackStop();
			me->GetMotionMaster()->Clear();
			me->StopMoving();
			_newTargetSelectTimer = 1000;
		}

		void SpellHitTarget(Unit* /*target*/, SpellInfo const* spell)
		{
			if (!_newTargetSelectTimer && spell->Id == sSpellMgr->GetSpellIdForDifficulty(_hitTargetSpellId, me))
				SelectNewTarget();
		}

		void SpellHit(Unit* /*caster*/, SpellInfo const* spell)
		{
			if (spell->Id == SPELL_TEAR_GAS_CREATURE)
				SelectNewTarget();
		}

		void UpdateAI(uint32 diff)
		{
			if (!_newTargetSelectTimer)
			{
				if ((!me->HasUnitState(UNIT_STATE_CASTING) && !me->GetVictim()) || !me->IsNonMeleeSpellCast(false, false, true, false, true))
					SelectNewTarget();
				else if (targetGUID)
				{
					Unit* target = ObjectAccessor::GetUnit(*me, targetGUID);
					if (me->GetVictim()->GetGUID() != targetGUID || !target || !me->IsValidAttackTarget(target) || target->HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH) || target->GetExactDist2dSq(4356.0f, 3211.0f) > 80.0f*80.0f || target->GetPositionZ() < 380.0f || target->GetPositionZ() > 405.0f)
						SelectNewTarget();
				}
			}

			DoMeleeAttackIfReady();

			if (!_newTargetSelectTimer)
				return;

			if (me->HasAura(SPELL_TEAR_GAS_CREATURE))
				return;

			if (_newTargetSelectTimer <= diff)
			{
				_newTargetSelectTimer = 0;
				CastMainSpell();
			}
			else
				_newTargetSelectTimer -= diff;
		}

		virtual void CastMainSpell() = 0;

	private:
		uint32 _hitTargetSpellId;
		uint32 _newTargetSelectTimer;
};

class npc_volatile_ooze : public CreatureScript
{
	public:
		npc_volatile_ooze() : CreatureScript("npc_volatile_ooze") { }

		struct npc_volatile_oozeAI : public npc_putricide_oozeAI
		{
			npc_volatile_oozeAI(Creature* creature) : npc_putricide_oozeAI(creature, SPELL_OOZE_ERUPTION)
			{
			}

			void CastMainSpell()
			{
				me->CastSpell(me, SPELL_VOLATILE_OOZE_ADHESIVE, false);
			}
		};

		CreatureAI* GetAI(Creature* creature) const
		{
			return GetIcecrownCitadelAI<npc_volatile_oozeAI>(creature);
		}
};

class npc_gas_cloud : public CreatureScript
{
	public:
		npc_gas_cloud() : CreatureScript("npc_gas_cloud") { }

		struct npc_gas_cloudAI : public npc_putricide_oozeAI
		{
			npc_gas_cloudAI(Creature* creature) : npc_putricide_oozeAI(creature, SPELL_EXPUNGED_GAS)
			{
				_newTargetSelectTimer = 0;
			}

			void CastMainSpell()
			{
				me->CastCustomSpell(SPELL_GASEOUS_BLOAT, SPELLVALUE_AURA_STACK, 10, me, false);
			}

		private:
			uint32 _newTargetSelectTimer;
		};

		CreatureAI* GetAI(Creature* creature) const
		{
			return GetIcecrownCitadelAI<npc_gas_cloudAI>(creature);
		}
};

class spell_putricide_slime_puddle : public SpellScriptLoader
{
	public:
		spell_putricide_slime_puddle() : SpellScriptLoader("spell_putricide_slime_puddle") { }

		class spell_putricide_slime_puddle_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_slime_puddle_SpellScript);

			void ScaleRange(std::list<WorldObject*>& targets)
			{
				targets.remove_if(Trinity::AllWorldObjectsInExactRange(GetCaster(), 2.5f * GetCaster()->GetFloatValue(OBJECT_FIELD_SCALE_X), true));
			}

			// big hax to unlock Abomination Eat Ooze ability, requires caster aura spell from difficulty X, but unlocks clientside when got base aura
			void HandleScript(SpellEffIndex effIndex)
			{
				const SpellInfo* s1 = sSpellMgr->GetSpellInfo(70346);
				const SpellInfo* s2 = sSpellMgr->GetSpellInfo(72456);
				if (s1 && s2)
					if (Unit* target = GetHitUnit())
					{
						Aura::TryRefreshStackOrCreate(s1, MAX_EFFECT_MASK, target, target);
						Aura::TryRefreshStackOrCreate(s2, MAX_EFFECT_MASK, target, target);
					}
			}

			void Register()
			{
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_slime_puddle_SpellScript::ScaleRange, EFFECT_0, TARGET_UNIT_DEST_AREA_ENEMY);
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_slime_puddle_SpellScript::ScaleRange, EFFECT_1, TARGET_UNIT_DEST_AREA_ENTRY);
				OnEffectHitTarget += SpellEffectFn(spell_putricide_slime_puddle_SpellScript::HandleScript, EFFECT_1, SPELL_EFFECT_APPLY_AURA);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_slime_puddle_SpellScript();
		}
};

class spell_putricide_slime_puddle_spawn : public SpellScriptLoader
{
	public:
		spell_putricide_slime_puddle_spawn() : SpellScriptLoader("spell_putricide_slime_puddle_spawn") { }

		class spell_putricide_slime_puddle_spawn_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_slime_puddle_spawn_SpellScript);

			void SelectDest()
			{
				if (Position* dest = const_cast<WorldLocation*>(GetExplTargetDest()))
				{
					float destZ = 395.0f; // random number close to ground, get exact in next call
					GetCaster()->UpdateGroundPositionZ(dest->GetPositionX(), dest->GetPositionY(), destZ);
					dest->m_positionZ = destZ;
				}
			}

			void Register()
			{
				BeforeCast += SpellCastFn(spell_putricide_slime_puddle_spawn_SpellScript::SelectDest);
			}

			uint32 _targetCount;
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_slime_puddle_spawn_SpellScript();
		}
};

class spell_putricide_grow_stacker : public SpellScriptLoader
{
	public:
		spell_putricide_grow_stacker() : SpellScriptLoader("spell_putricide_grow_stacker") { }

		class spell_putricide_grow_stacker_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_putricide_grow_stacker_AuraScript);

			void HandleTriggerSpell(AuraEffect const* aurEff)
			{
				if (Unit* target = GetTarget())
					if (target->HasAura(SPELL_TEAR_GAS_CREATURE))
						PreventDefaultAction();
			}

			void Register()
			{
				OnEffectPeriodic += AuraEffectPeriodicFn(spell_putricide_grow_stacker_AuraScript::HandleTriggerSpell, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
			}
		};

		AuraScript* GetAuraScript() const
		{
			return new spell_putricide_grow_stacker_AuraScript();
		}
};

class spell_putricide_unstable_experiment : public SpellScriptLoader
{
	public:
		spell_putricide_unstable_experiment() : SpellScriptLoader("spell_putricide_unstable_experiment") { }

		class spell_putricide_unstable_experiment_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_unstable_experiment_SpellScript);

			void HandleScript(SpellEffIndex effIndex)
			{
				PreventHitDefaultEffect(effIndex);
				if (GetCaster()->GetTypeId() != TYPEID_UNIT)
					return;

				Creature* creature = GetCaster()->ToCreature();

				uint8 stage = creature->AI()->GetData(DATA_EXPERIMENT_STAGE);
				creature->AI()->SetData(DATA_EXPERIMENT_STAGE, stage ? 0 : 1);

				Creature* target = NULL;
				std::list<Creature*> creList;
				GetCreatureListWithEntryInGrid(creList, GetCaster(), NPC_ABOMINATION_WING_MAD_SCIENTIST_STALKER, 200.0f);
				for (std::list<Creature*>::iterator itr = creList.begin(); itr != creList.end(); ++itr)
					if ((*itr)->GetPositionX() > 4350.0f && stage == 0 || (*itr)->GetPositionX() < 4350.0f && stage == 1)
					{
						target = (*itr);
						break;
					}

				if (Aura* aura = target->GetAura(uint32(GetSpellInfo()->Effects[stage].CalcValue())))
					if (aura->GetOwner() == target) // avoid assert(false) at any cost
						aura->UpdateOwner(5000, target); // update whole aura so previous periodic ticks before refreshed by new one

				GetCaster()->CastSpell(target, uint32(GetSpellInfo()->Effects[stage].CalcValue()), true, NULL, NULL, GetCaster()->GetGUID());
			}

			void Register()
			{
				OnEffectHitTarget += SpellEffectFn(spell_putricide_unstable_experiment_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_unstable_experiment_SpellScript();
		}
};

class spell_putricide_tear_gas_effect : public SpellScriptLoader
{
	public:
		spell_putricide_tear_gas_effect() : SpellScriptLoader("spell_putricide_tear_gas_effect") { }

		class spell_putricide_tear_gas_effect_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_tear_gas_effect_SpellScript);

			void FilterTargets(std::list<WorldObject*>& targets)
			{
				// vanish rank 1-3, mage invisibility
				targets.remove_if(Trinity::UnitAuraCheck(true, 11327));
				targets.remove_if(Trinity::UnitAuraCheck(true, 11329));
				targets.remove_if(Trinity::UnitAuraCheck(true, 26888));
				targets.remove_if(Trinity::UnitAuraCheck(true, 32612));
			}

			void Register()
			{
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_tear_gas_effect_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_tear_gas_effect_SpellScript::FilterTargets, EFFECT_1, TARGET_UNIT_SRC_AREA_ENEMY);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_tear_gas_effect_SpellScript();
		}
};

class spell_putricide_gaseous_bloat : public SpellScriptLoader
{
	public:
		spell_putricide_gaseous_bloat() : SpellScriptLoader("spell_putricide_gaseous_bloat") { }

		class spell_putricide_gaseous_bloat_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_putricide_gaseous_bloat_AuraScript);

			void HandleExtraEffect(AuraEffect const* /*aurEff*/)
			{
				Unit* target = GetTarget();
				target->RemoveAuraFromStack(GetSpellInfo()->Id, GetCasterGUID());
				/*if (!target->HasAura(GetId()))
					if (Unit* caster = GetCaster())
						caster->CastCustomSpell(SPELL_GASEOUS_BLOAT, SPELLVALUE_AURA_STACK, 10, caster, false);*/
			}

			void Register()
			{
				OnEffectPeriodic += AuraEffectPeriodicFn(spell_putricide_gaseous_bloat_AuraScript::HandleExtraEffect, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE);
			}
		};

		AuraScript* GetAuraScript() const
		{
			return new spell_putricide_gaseous_bloat_AuraScript();
		}
};

class spell_putricide_ooze_channel : public SpellScriptLoader
{
	public:
		spell_putricide_ooze_channel() : SpellScriptLoader("spell_putricide_ooze_channel") { }

		class spell_putricide_ooze_channel_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_ooze_channel_SpellScript);

			bool Validate(SpellInfo const* spell)
			{
				if (!spell->ExcludeTargetAuraSpell)
					return false;
				if (!sSpellMgr->GetSpellInfo(spell->ExcludeTargetAuraSpell))
					return false;
				return true;
			}

			// set up initial variables and check if caster is creature
			// this will let use safely use ToCreature() casts in entire script
			bool Load()
			{
				_target = NULL;
				return GetCaster()->GetTypeId() == TYPEID_UNIT;
			}

			void SelectTarget(std::list<WorldObject*>& targets)
			{
				// dbc has only 1 field for excluding, this will prevent anyone from getting both at the same time
				targets.remove_if(Trinity::UnitAuraCheck(true, SPELL_VOLATILE_OOZE_PROTECTION));
				targets.remove_if(Trinity::UnitAuraCheck(true, SPELL_GASEOUS_BLOAT_PROTECTION));

				if (targets.empty())
				{
					FinishCast(SPELL_FAILED_NO_VALID_TARGETS);
					GetCaster()->ToCreature()->DespawnOrUnsummon(1);    // despawn next update
					return;
				}

				WorldObject* target = Trinity::Containers::SelectRandomContainerElement(targets);
				targets.clear();
				targets.push_back(target);
				_target = target;
			}

			void SetTarget(std::list<WorldObject*>& targets)
			{
				targets.clear();
				if (_target)
					targets.push_back(_target);
			}

			void StartAttack()
			{
				GetCaster()->ClearUnitState(UNIT_STATE_CASTING);
				GetCaster()->DeleteThreatList();
				GetCaster()->ToCreature()->SetInCombatWithZone();
				GetCaster()->ToCreature()->AI()->AttackStart(GetHitUnit());
				GetCaster()->AddThreat(GetHitUnit(), 500000000.0f);    // value seen in sniff
				if (Creature* c = GetCaster()->ToCreature())
					c->AI()->SetGUID(GetHitUnit()->GetGUID(), -1);
			}

			void Register()
			{
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_ooze_channel_SpellScript::SelectTarget, EFFECT_0, TARGET_UNIT_SRC_AREA_ENEMY);
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_ooze_channel_SpellScript::SetTarget, EFFECT_1, TARGET_UNIT_SRC_AREA_ENEMY);
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_ooze_channel_SpellScript::SetTarget, EFFECT_2, TARGET_UNIT_SRC_AREA_ENEMY);
				AfterHit += SpellHitFn(spell_putricide_ooze_channel_SpellScript::StartAttack);
			}

			WorldObject* _target;
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_ooze_channel_SpellScript();
		}
};

class spell_putricide_ooze_eruption_searcher : public SpellScriptLoader
{
	public:
		spell_putricide_ooze_eruption_searcher() : SpellScriptLoader("spell_putricide_ooze_eruption_searcher") { }

		class spell_putricide_ooze_eruption_searcher_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_ooze_eruption_searcher_SpellScript);

			void HandleDummy(SpellEffIndex /*effIndex*/)
			{
				uint32 adhesiveId = sSpellMgr->GetSpellIdForDifficulty(SPELL_VOLATILE_OOZE_ADHESIVE, GetCaster());
				if (GetHitUnit()->HasAura(adhesiveId))
				{
					GetHitUnit()->RemoveAurasDueToSpell(adhesiveId, GetCaster()->GetGUID(), 0, AURA_REMOVE_BY_ENEMY_SPELL);
					GetCaster()->CastSpell(GetHitUnit(), SPELL_OOZE_ERUPTION, true);
				}
			}

			void Register()
			{
				OnEffectHitTarget += SpellEffectFn(spell_putricide_ooze_eruption_searcher_SpellScript::HandleDummy, EFFECT_0, SPELL_EFFECT_DUMMY);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_ooze_eruption_searcher_SpellScript();
		}
};

class spell_putricide_mutated_plague : public SpellScriptLoader
{
	public:
		spell_putricide_mutated_plague() : SpellScriptLoader("spell_putricide_mutated_plague") { }

		class spell_putricide_mutated_plague_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_putricide_mutated_plague_AuraScript);

			void HandleTriggerSpell(AuraEffect const* aurEff)
			{
				PreventDefaultAction();
				Unit* caster = GetCaster();
				if (!caster)
					return;

				uint32 triggerSpell = GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell;
				SpellInfo const* spell = sSpellMgr->GetSpellInfo(triggerSpell);
				spell = sSpellMgr->GetSpellForDifficultyFromSpell(spell, caster);

				int32 damage = spell->Effects[EFFECT_0].CalcValue(caster);
				damage = damage * pow(2.5f, GetStackAmount());

				GetTarget()->CastCustomSpell(triggerSpell, SPELLVALUE_BASE_POINT0, damage, GetTarget(), true, NULL, aurEff, GetCasterGUID());
			}

			void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
			{
				uint32 healSpell = uint32(GetSpellInfo()->Effects[EFFECT_0].CalcValue());
				SpellInfo const* spell = sSpellMgr->GetSpellInfo(healSpell);
				if (!spell)
					return;
				spell = sSpellMgr->GetSpellForDifficultyFromSpell(spell, GetTarget());
				int32 healAmount = spell->Effects[EFFECT_0].CalcValue();
				healAmount *= GetStackAmount();
				GetTarget()->CastCustomSpell(healSpell, SPELLVALUE_BASE_POINT0, healAmount, GetTarget(), TRIGGERED_FULL_MASK, NULL, NULL, GetCasterGUID());
			}

			void Register()
			{
				OnEffectPeriodic += AuraEffectPeriodicFn(spell_putricide_mutated_plague_AuraScript::HandleTriggerSpell, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL);
				AfterEffectRemove += AuraEffectRemoveFn(spell_putricide_mutated_plague_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL);
			}
		};

		AuraScript* GetAuraScript() const
		{
			return new spell_putricide_mutated_plague_AuraScript();
		}
};

class spell_putricide_unbound_plague : public SpellScriptLoader
{
	public:
		spell_putricide_unbound_plague() : SpellScriptLoader("spell_putricide_unbound_plague") { }

		class spell_putricide_unbound_plague_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_unbound_plague_SpellScript);

			bool Validate(SpellInfo const* /*spell*/)
			{
				if (!sSpellMgr->GetSpellInfo(SPELL_UNBOUND_PLAGUE))
					return false;
				if (!sSpellMgr->GetSpellInfo(SPELL_UNBOUND_PLAGUE_SEARCHER))
					return false;
				return true;
			}

			void FilterTargets(std::list<WorldObject*>& targets)
			{
				if (AuraEffect const* eff = GetCaster()->GetAuraEffect(SPELL_UNBOUND_PLAGUE_SEARCHER, EFFECT_0))
				{
					if (eff->GetTickNumber() < 2)
					{
						targets.clear();
						return;
					}
				}


				targets.remove_if(Trinity::UnitAuraCheck(true, sSpellMgr->GetSpellIdForDifficulty(SPELL_UNBOUND_PLAGUE, GetCaster())));
				Trinity::Containers::RandomResizeList(targets, 1);
			}

			void HandleScript(SpellEffIndex /*effIndex*/)
			{
				if (!GetHitUnit())
					return;

				InstanceScript* instance = GetCaster()->GetInstanceScript();
				if (!instance)
					return;

				uint32 plagueId = sSpellMgr->GetSpellIdForDifficulty(SPELL_UNBOUND_PLAGUE, GetCaster());

				if (!GetHitUnit()->HasAura(plagueId))
				{
					if (Creature* professor = ObjectAccessor::GetCreature(*GetCaster(), instance->GetData64(DATA_PROFESSOR_PUTRICIDE)))
					{
						if (Aura* oldPlague = GetCaster()->GetAura(plagueId, professor->GetGUID()))
						{
							if (Aura* newPlague = professor->AddAura(plagueId, GetHitUnit()))
							{
								newPlague->SetMaxDuration(oldPlague->GetMaxDuration());
								newPlague->SetDuration(oldPlague->GetDuration());
								oldPlague->Remove();
								GetCaster()->RemoveAurasDueToSpell(SPELL_UNBOUND_PLAGUE_SEARCHER);
								GetCaster()->CastSpell(GetCaster(), SPELL_PLAGUE_SICKNESS, true);
								GetCaster()->CastSpell(GetCaster(), SPELL_UNBOUND_PLAGUE_PROTECTION, true);
								professor->CastSpell(GetHitUnit(), SPELL_UNBOUND_PLAGUE_SEARCHER, true);
							}
						}
					}
				}
			}

			void Register()
			{
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_unbound_plague_SpellScript::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY);
				OnEffectHitTarget += SpellEffectFn(spell_putricide_unbound_plague_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_unbound_plague_SpellScript();
		}
};

class spell_putricide_unbound_plague_dmg : public SpellScriptLoader
{
	public:
		spell_putricide_unbound_plague_dmg() : SpellScriptLoader("spell_putricide_unbound_plague_dmg") { }

		class spell_putricide_unbound_plague_dmg_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_putricide_unbound_plague_dmg_AuraScript);

			void HandlePeriodic(AuraEffect* aurEff)
			{
				int32 baseAmt = aurEff->GetSpellInfo()->Effects[0].CalcValue();
				int32 dmg = int32(baseAmt * pow(1.25f, float(aurEff->GetTickNumber())));
				if (dmg <= 0) // safety check, impossible
					return;
				aurEff->SetAmount(dmg);
			}

			void Register()
			{
				OnEffectUpdatePeriodic += AuraEffectUpdatePeriodicFn(spell_putricide_unbound_plague_dmg_AuraScript::HandlePeriodic, EFFECT_0, SPELL_AURA_PERIODIC_DAMAGE);
			}
		};

		AuraScript* GetAuraScript() const
		{
			return new spell_putricide_unbound_plague_dmg_AuraScript();
		}
};

class spell_putricide_choking_gas_bomb : public SpellScriptLoader
{
	public:
		spell_putricide_choking_gas_bomb() : SpellScriptLoader("spell_putricide_choking_gas_bomb") { }

		class spell_putricide_choking_gas_bomb_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_choking_gas_bomb_SpellScript);

			void HandleScript(SpellEffIndex /*effIndex*/)
			{
				uint32 skipIndex = urand(0, 2);
				for (uint32 i = 0; i < 3; ++i)
				{
					if (i == skipIndex)
						continue;

					uint32 spellId = uint32(GetSpellInfo()->Effects[i].CalcValue());
					GetCaster()->CastSpell(GetCaster(), spellId, true, NULL, NULL, GetCaster()->GetGUID());
				}
			}

			void Register()
			{
				OnEffectHitTarget += SpellEffectFn(spell_putricide_choking_gas_bomb_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_choking_gas_bomb_SpellScript();
		}
};

// Removes aura with id stored in effect value
class spell_putricide_clear_aura_effect_value : public SpellScriptLoader
{
	public:
		spell_putricide_clear_aura_effect_value() : SpellScriptLoader("spell_putricide_clear_aura_effect_value") { }

		class spell_putricide_clear_aura_effect_value_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_clear_aura_effect_value_SpellScript);

			void HandleScript(SpellEffIndex effIndex)
			{
				PreventHitDefaultEffect(effIndex);
				uint32 auraId = sSpellMgr->GetSpellIdForDifficulty(uint32(GetEffectValue()), GetCaster());
				GetHitUnit()->RemoveAurasDueToSpell(auraId);
			}

			void Register()
			{
				OnEffectHitTarget += SpellEffectFn(spell_putricide_clear_aura_effect_value_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_clear_aura_effect_value_SpellScript();
		}
};

class spell_putricide_mutation_init : public SpellScriptLoader
{
	public:
		spell_putricide_mutation_init() : SpellScriptLoader("spell_putricide_mutation_init") { }

		class spell_putricide_mutation_init_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_mutation_init_SpellScript);

			SpellCastResult CheckRequirementInternal(SpellCustomErrors& extendedError)
			{
				InstanceScript* instance = GetExplTargetUnit()->GetInstanceScript();
				if (!instance)
					return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW;

				Creature* professor = ObjectAccessor::GetCreature(*GetExplTargetUnit(), instance->GetData64(DATA_PROFESSOR_PUTRICIDE));
				if (!professor)
					return SPELL_FAILED_CANT_DO_THAT_RIGHT_NOW;

				if (professor->AI()->GetData(DATA_PHASE) == 3 || !professor->IsAlive())
				{
					extendedError = SPELL_CUSTOM_ERROR_ALL_POTIONS_USED;
					return SPELL_FAILED_CUSTOM_ERROR;
				}

				if (professor->AI()->GetData(DATA_ABOMINATION))
				{
					extendedError = SPELL_CUSTOM_ERROR_TOO_MANY_ABOMINATIONS;
					return SPELL_FAILED_CUSTOM_ERROR;
				}

				return SPELL_CAST_OK;
			}

			SpellCastResult CheckRequirement()
			{
				if (!GetExplTargetUnit())
					return SPELL_FAILED_BAD_TARGETS;

				if (GetExplTargetUnit()->GetTypeId() != TYPEID_PLAYER)
					return SPELL_FAILED_TARGET_NOT_PLAYER;

				SpellCustomErrors extension = SPELL_CUSTOM_ERROR_NONE;
				SpellCastResult result = CheckRequirementInternal(extension);
				if (result != SPELL_CAST_OK)
				{
					Spell::SendCastResult(GetExplTargetUnit()->ToPlayer(), GetSpellInfo(), 0, result, extension);
					return result;
				}

				return SPELL_CAST_OK;
			}

			void Register()
			{
				OnCheckCast += SpellCheckCastFn(spell_putricide_mutation_init_SpellScript::CheckRequirement);
			}
		};

		class spell_putricide_mutation_init_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_putricide_mutation_init_AuraScript);

			void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
			{
				uint32 spellId = 70311;
				if (GetTarget()->GetMap()->GetSpawnMode() & 1)
					spellId = 71503;

				GetTarget()->CastSpell(GetTarget(), spellId, true);
			}

			void Register()
			{
				AfterEffectRemove += AuraEffectRemoveFn(spell_putricide_mutation_init_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_mutation_init_SpellScript();
		}

		AuraScript* GetAuraScript() const
		{
			return new spell_putricide_mutation_init_AuraScript();
		}
};

class spell_putricide_mutated_transformation : public SpellScriptLoader
{
	public:
		spell_putricide_mutated_transformation() : SpellScriptLoader("spell_putricide_mutated_transformation") { }

		class spell_putricide_mutated_transformation_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_mutated_transformation_SpellScript);

			void HandleSummon(SpellEffIndex effIndex)
			{
				PreventHitDefaultEffect(effIndex);
				Unit* caster = GetOriginalCaster();
				if (!caster)
					return;

				InstanceScript* instance = caster->GetInstanceScript();
				if (!instance)
					return;

				Creature* putricide = ObjectAccessor::GetCreature(*caster, instance->GetData64(DATA_PROFESSOR_PUTRICIDE));
				if (!putricide)
					return;

				if (putricide->AI()->GetData(DATA_ABOMINATION))
				{
					if (Player* player = caster->ToPlayer())
						Spell::SendCastResult(player, GetSpellInfo(), 0, SPELL_FAILED_CUSTOM_ERROR, SPELL_CUSTOM_ERROR_TOO_MANY_ABOMINATIONS);
					return;
				}

				if (!putricide->IsInCombat())
					putricide->SetInCombatWithZone();

				uint32 entry = uint32(GetSpellInfo()->Effects[effIndex].MiscValue);
				SummonPropertiesEntry const* properties = sSummonPropertiesStore.LookupEntry(uint32(GetSpellInfo()->Effects[effIndex].MiscValueB));
				uint32 duration = uint32(GetSpellInfo()->GetDuration());

				Position pos;
				caster->GetPosition(&pos);
				TempSummon* summon = caster->GetMap()->SummonCreature(entry, pos, properties, duration, caster, GetSpellInfo()->Id);
				if (!summon || !summon->IsVehicle())
					return;

				summon->CastSpell(summon, SPELL_ABOMINATION_VEHICLE_POWER_DRAIN, true);
				summon->CastSpell(summon, SPELL_MUTATED_TRANSFORMATION_DAMAGE, true);
				caster->CastSpell(summon, SPELL_MUTATED_TRANSFORMATION_NAME, true);

				//EnterVehicle(summon, 0);    // VEHICLE_SPELL_RIDE_HARDCODED is used according to sniff, this is ok
				caster->CastCustomSpell(VEHICLE_SPELL_RIDE_HARDCODED, SPELLVALUE_BASE_POINT0, 1, summon, TRIGGERED_FULL_MASK); 
				summon->SetCreatorGUID(caster->GetGUID());
				putricide->AI()->JustSummoned(summon);

				summon->setPowerType(POWER_ENERGY);
				summon->SetMaxPower(POWER_ENERGY, 100);
				summon->SetPower(POWER_ENERGY, 0);
				summon->SetStatFloatValue(UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER, 0);
				summon->SetStatFloatValue(UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER, 0);
			}

			void Register()
			{
				OnEffectHit += SpellEffectFn(spell_putricide_mutated_transformation_SpellScript::HandleSummon, EFFECT_0, SPELL_EFFECT_SUMMON);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_mutated_transformation_SpellScript();
		}
};

class spell_putricide_mutated_transformation_dismiss : public SpellScriptLoader
{
	public:
		spell_putricide_mutated_transformation_dismiss() : SpellScriptLoader("spell_putricide_mutated_transformation_dismiss") { }

		class spell_putricide_mutated_transformation_dismiss_AuraScript : public AuraScript
		{
			PrepareAuraScript(spell_putricide_mutated_transformation_dismiss_AuraScript);

			void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
			{
				if (Vehicle* veh = GetTarget()->GetVehicleKit())
					veh->RemoveAllPassengers();
			}

			void Register()
			{
				AfterEffectRemove += AuraEffectRemoveFn(spell_putricide_mutated_transformation_dismiss_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_PERIODIC_TRIGGER_SPELL, AURA_EFFECT_HANDLE_REAL);
			}
		};

		AuraScript* GetAuraScript() const
		{
			return new spell_putricide_mutated_transformation_dismiss_AuraScript();
		}
};

class spell_putricide_mutated_transformation_dmg : public SpellScriptLoader
{
	public:
		spell_putricide_mutated_transformation_dmg() : SpellScriptLoader("spell_putricide_mutated_transformation_dmg") { }

		class spell_putricide_mutated_transformation_dmg_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_mutated_transformation_dmg_SpellScript);

			void FilterTargetsInitial(std::list<WorldObject*>& targets)
			{
				if (Unit* owner = ObjectAccessor::GetUnit(*GetCaster(), GetCaster()->GetCreatorGUID()))
					targets.remove(owner);
			}

			void Register()
			{
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_mutated_transformation_dmg_SpellScript::FilterTargetsInitial, EFFECT_0, TARGET_UNIT_SRC_AREA_ALLY);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_mutated_transformation_dmg_SpellScript();
		}
};

class spell_putricide_eat_ooze : public SpellScriptLoader
{
	public:
		spell_putricide_eat_ooze() : SpellScriptLoader("spell_putricide_eat_ooze") { }

		class spell_putricide_eat_ooze_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_eat_ooze_SpellScript);

			void SelectTarget(std::list<WorldObject*>& targets)
			{
				if (targets.empty())
					return;

				targets.sort(Trinity::ObjectDistanceOrderPred(GetCaster()));
				WorldObject* target = targets.front();
				targets.clear();
				targets.push_back(target);
			}

			void HandleScript(SpellEffIndex /*effIndex*/)
			{
				Creature* target = GetHitCreature();
				if (!target)
					return;

				if (Aura* grow = target->GetAura(uint32(GetEffectValue())))
				{
					if (grow->GetStackAmount() <= 4)
					{
						target->RemoveAurasDueToSpell(SPELL_GROW_STACKER);
						target->RemoveAura(grow);
						target->DespawnOrUnsummon(1);
					}
					else
						grow->ModStackAmount(-4);
				}
			}

			void Register()
			{
				OnEffectHitTarget += SpellEffectFn(spell_putricide_eat_ooze_SpellScript::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
				OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_putricide_eat_ooze_SpellScript::SelectTarget, EFFECT_0, TARGET_UNIT_DEST_AREA_ENTRY);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_eat_ooze_SpellScript();
		}
};

class spell_putricide_regurgitated_ooze : public SpellScriptLoader
{
	public:
		spell_putricide_regurgitated_ooze() : SpellScriptLoader("spell_putricide_regurgitated_ooze") { }

		class spell_putricide_regurgitated_ooze_SpellScript : public SpellScript
		{
			PrepareSpellScript(spell_putricide_regurgitated_ooze_SpellScript);

			// the only purpose of this hook is to fail the achievement
			void ExtraEffect(SpellEffIndex /*effIndex*/)
			{
				if (InstanceScript* instance = GetCaster()->GetInstanceScript())
					instance->SetData(DATA_NAUSEA_ACHIEVEMENT, uint32(false));
			}

			void Register()
			{
				OnEffectHitTarget += SpellEffectFn(spell_putricide_regurgitated_ooze_SpellScript::ExtraEffect, EFFECT_0, SPELL_EFFECT_APPLY_AURA);
			}
		};

		SpellScript* GetSpellScript() const
		{
			return new spell_putricide_regurgitated_ooze_SpellScript();
		}
};

void AddSC_boss_professor_putricide()
{
	new boss_professor_putricide();
	new npc_volatile_ooze();
	new npc_gas_cloud();
	new spell_putricide_slime_puddle();
	new spell_putricide_slime_puddle_spawn();
	new spell_putricide_grow_stacker();
	new spell_putricide_unstable_experiment();
	new spell_putricide_tear_gas_effect();
	new spell_putricide_gaseous_bloat();
	new spell_putricide_ooze_channel();
	new spell_putricide_ooze_eruption_searcher();
	new spell_putricide_mutated_plague();
	new spell_putricide_unbound_plague();
	new spell_putricide_unbound_plague_dmg();
	new spell_putricide_choking_gas_bomb();
	new spell_putricide_clear_aura_effect_value();
	new spell_putricide_mutation_init();
	new spell_putricide_mutated_transformation();
	new spell_putricide_mutated_transformation_dismiss();
	new spell_putricide_mutated_transformation_dmg();
	new spell_putricide_eat_ooze();
	new spell_putricide_regurgitated_ooze();
}